/*-*-C-*-
 * Constrained moves for the Windows CSE
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"
#include "dragdrop.h"
#include "interactor.h"

#include "format.h"
#include "windowedit.h"
#include "grid.h"
#include "move.h"
#include "protocol.h"


typedef struct
{
    WindowObjPtr window;
    Bool allowvertical;    /* TRUE if vertical movement permitted */
    Bool allowhorizontal;  /* TRUE if horizontal movement permitted */
    PointRec current;      /* current position of mouse (work) */
    PointRec offset;       /* current offset from original position */
    RectRec workbox;       /* current bbox of selected gadgets (work) */
} MoveClosureRec, *MoveClosurePtr;



/*
 * Move selected gadgets in 'window' by 'offset'.
 * On entry, 'newbbox' contains the work co-ordinates of the new bounding
 *  box for the selected gadgets.
 */

static error * move_selected_gadgets (
    WindowObjPtr window,
    PointPtr offset,
    RectPtr newbbox
)
{
    RectRec invalid;
    RectRec original;

    /* determine coordinates of original bounding box */
    original.minx = newbbox->minx - offset->x;
    original.maxx = newbbox->maxx - offset->x;
    original.miny = newbbox->miny - offset->y;
    original.maxy = newbbox->maxy - offset->y;

    /* align the offset */
    wimp_align_point (offset);

    /* round offset to nearest multiple of grid spacing if locked */
    if (window->grid.lock)
        grid_snap_point (window, offset);

    /* nothing to do if offset is zero */
    if (offset->x == 0 && offset->y == 0)
        return NULL;

    /* invalidate - allowing for ears */
    windowedit_add_ears_to_bbox (&original, &invalid);
    ER ( wimp_invalidate (window->window, &invalid) );

    /* determine new bbox and invalidate */
    original.minx += offset->x;
    original.maxx += offset->x;
    original.miny += offset->y;
    original.maxy += offset->y;

    windowedit_add_ears_to_bbox (&original, &invalid);
    ER ( wimp_invalidate (window->window, &invalid) );

    /* move the selected gadgets */
    {
        GadgetPtr gadget = window->gadgets;

        while (gadget != NULL)
        {
            if (gadget->selected)
            {
                gadget->hdr.bbox.minx += offset->x;
                gadget->hdr.bbox.maxx += offset->x;
                gadget->hdr.bbox.miny += offset->y;
                gadget->hdr.bbox.maxy += offset->y;
            }

            gadget = gadget->next;
        }
    }

    /* note probable modification */
    ER ( protocol_send_resed_object_modified (window) );

    return NULL;
}


/*
 * Interactor for a (possibly) constrained move operation
 */

static error * move_interactor (
    unsigned int event,
    int *buf,
    void *closure,
    Bool *consumed
)
{
    MoveClosurePtr move = (MoveClosurePtr) closure;
    static Bool donepointer = FALSE;
    Bool removeptr;

    if (buf == NULL)            /* we are being asked to cancel */
    {
        /* reset pointer to normal if necessary */
        if (donepointer)
        {
            dragdrop_normal_pointer ();
            donepointer = FALSE;
        }

        /* remove drag box from window */
        wimp_update_eor_box (move->window->window, &move->workbox);

        /* reset auto-scroll functions */
        (void) dragdrop_scroll (NULL, NULL, NULL);

        /* cancel wimp dragbox operation */
        return swi(Wimp_DragBox,  R1, 0,  END);
    }
    
    switch (event)
    {
    case EV_NULL_REASON_CODE:
        {
            PointerInfoRec pointer;
            PointRec work, delta;
            PointRec oldscroll, newscroll;
            Bool abouttoscroll;

            (void) swi (Wimp_GetPointerInfo,  R1, &pointer,  END);

            /*
             * Look to see if scrolling is about to happen.
             * If so, then we wish only to remove the drag box: it will be
             *  redrawn by the REDRAW_WINDOW_REQUEST code.
             * The auto-scrolling routines actually update the scroll offset,
             *  so we must preserve the original and reinstate afterwards.
             */

            oldscroll = move->window->window->scrolloffset;
            abouttoscroll = dragdrop_scroll (move->window->window,
                                             &pointer.position, &removeptr);
            if (abouttoscroll)
            {
                /* take note of new scroll offset before restoring */
                newscroll = move->window->window->scrolloffset;
                move->window->window->scrolloffset = oldscroll;
            }
            
            wimp_convert_point (ScreenToWork, move->window->window,
                                                 &pointer.position, &work);

            delta.x = (move->allowhorizontal) ? work.x - move->current.x : 0;
            delta.y = (move->allowvertical) ? work.y - move->current.y : 0;

            if (move->window->grid.lock)
                grid_snap_point (move->window, &delta);

            if (!(delta.x == 0 && delta.y == 0) || abouttoscroll)
            {
                move->current.x += delta.x;
                move->current.y += delta.y;
                move->offset.x += delta.x;
                move->offset.y += delta.y;

                wimp_update_eor_box (move->window->window,
                                                 &move->workbox);
                move->workbox.minx += delta.x;
                move->workbox.maxx += delta.x;
                move->workbox.miny += delta.y;
                move->workbox.maxy += delta.y;

                /* don't plot new drag box if about to scroll */
                if (!abouttoscroll)
                    wimp_update_eor_box (move->window->window,
                                                   &move->workbox);
            }
            else
            {
                wimp_start_rotate_box ();
                wimp_update_eor_box (move->window->window,
                                                 &move->workbox);
                wimp_end_rotate_box ();
            }

            if (abouttoscroll)
            {
                /* restore new scroll offset ready for redraw code */
                move->window->window->scrolloffset = newscroll;

                if (donepointer == FALSE)
                {
                    dragdrop_scroll_pointer ();
                    donepointer = TRUE;
                }
            }
            if (donepointer && removeptr)
            {
                dragdrop_normal_pointer ();
                donepointer = FALSE;
            }
        }
        break;

    case EV_REDRAW_WINDOW_REQUEST:
        {
            WindowRedrawPtr redraw = (WindowRedrawPtr) buf;
            
            if (redraw->handle == move->window->window->handle)
            {
                PointerInfoRec pointer;
                PointRec work, delta;

                *consumed = TRUE;

                /* redraw exposed part of window */
                windowedit_redraw_window (redraw, move->window);

                /* determine revised position of drag box and draw it */
                (void) swi (Wimp_GetPointerInfo,  R1, &pointer,  END);
                
                wimp_convert_point (ScreenToWork, move->window->window,
                                                  &pointer.position, &work);

                delta.x = (move->allowhorizontal) ? 
                                           work.x - move->current.x : 0;
                delta.y = (move->allowvertical) ?
                                           work.y - move->current.y : 0;

                if (move->window->grid.lock)
                    grid_snap_point (move->window, &delta);

                move->current.x += delta.x;
                move->current.y += delta.y;
                move->offset.x += delta.x;
                move->offset.y += delta.y;

                move->workbox.minx += delta.x;
                move->workbox.maxx += delta.x;
                move->workbox.miny += delta.y;
                move->workbox.maxy += delta.y;

                wimp_update_eor_box (move->window->window,
                                                   &move->workbox);

                /* note modification */
                protocol_send_resed_object_modified (move->window);
            }
        }
        break;

    case EV_KEY_PRESSED:
        {
            KeyPressPtr key = (KeyPressPtr) buf;

            dragdrop_nudge (key->code, 4);

            /* move has priority over keyboard shortcuts */
            *consumed = (key->code != 0x1b);
        }
        break;

    case EV_USER_DRAG_BOX:
        {
            interactor_cancel();
            *consumed = TRUE;
            return move_selected_gadgets (move->window,
                                          &move->offset, &move->workbox);
        }
        break;
    }
    return NULL;
}


/*
 * Called to initiate a (possibly) constrained move of the current selection
 *  of gadgets.
 * Only vertical displacement is permitted if earnum is 1 or 7, and only
 *  horizontal displacement if earnum is 3 or 5; otherwise there is no
 *  constraint (other than that imposed by the mouse pointer remaining
 *  within the window).
 */

error * move_start (
    WindowObjPtr window,
    int earnum,
    PointPtr mousepos
)
{
    DragBoxRec drag;
    static MoveClosureRec closure;
    PointRec work;

    interactor_cancel();
    wimp_convert_point (ScreenToWork, window->window,
                                              mousepos, &work);
    closure.window = window;
    closure.allowvertical = (earnum / 3 != 1);
    closure.allowhorizontal = (earnum % 3 != 1);
    closure.current = work;
    closure.offset.x = 0;
    closure.offset.y = 0;
    windowedit_get_selection_bbox (window, &closure.workbox);

    wimp_update_eor_box (window->window, &closure.workbox);

    drag.windowhandle = window->window->handle;
    drag.type = 7;
    drag.constrain = window->window->visarea;

    ER ( swi (Wimp_DragBox,  R1, &drag,  END) );

    interactor_install (move_interactor, (void *) &closure);
    interactor_enable_events (BIT(EV_NULL_REASON_CODE));
    interactor_set_timeout (2);

    return NULL;
}
